None
Допустим, мы работаем в добывающей компании «ГлавРосГосНефть».
Нам предоставлены пробы нефти в трёх регионах: в каждом 10 000 месторождений, где измерили качество нефти и объём её запасов.
Шаги для выбора локации:
Условия задачи:
Признаки:
id — уникальный идентификатор скважины (для моделей не будет нужен);f0, f1, f2 — три признака точек (неважно, что они означают, но сами признаки значимы);Цель: Нужно решить, где бурить новые скважины.
Целевые признки:
product — объём запасов в скважине (тыс. баррелей).import pandas as pd
import numpy as np
# для графиков
import matplotlib.pyplot as plt
# метрика время исполнения
import time
# для скрытия ошибок
import warnings
# для разделения данных на выборки
from sklearn.model_selection import train_test_split
# метрика MSE
from sklearn.metrics import mean_squared_error
# Линейная регрессия
from sklearn.linear_model import LinearRegression
# полиморфирование, или увеличение степеней признаков (для линейной регрессии)
from sklearn.preprocessing import PolynomialFeatures
# для маштабирования признаков
from sklearn.preprocessing import StandardScaler
# для проверки гипотиз
from scipy import stats as st
# для скрытия ошибок
warnings.filterwarnings("ignore")
# pd.options.mode.chained_assignment = None
# для увеличения окна вывода
pd.options.display.max_rows = 300
Загрузим данные из трех файлов.
# версия для ревьюера
geo_data_0 = pd.read_csv('/datasets/geo_data_0.csv')
# моя локальная версия
# geo_data_0 = pd.read_csv('geo_data_0.csv')
geo_data_0.head()
| id | f0 | f1 | f2 | product | |
|---|---|---|---|---|---|
| 0 | txEyH | 0.705745 | -0.497823 | 1.221170 | 105.280062 |
| 1 | 2acmU | 1.334711 | -0.340164 | 4.365080 | 73.037750 |
| 2 | 409Wp | 1.022732 | 0.151990 | 1.419926 | 85.265647 |
| 3 | iJLyR | -0.032172 | 0.139033 | 2.978566 | 168.620776 |
| 4 | Xdl7t | 1.988431 | 0.155413 | 4.751769 | 154.036647 |
# версия для ревьюера
geo_data_1 = pd.read_csv('/datasets/geo_data_1.csv')
# моя локальная версия
# geo_data_1 = pd.read_csv('geo_data_1.csv')
geo_data_1.head()
| id | f0 | f1 | f2 | product | |
|---|---|---|---|---|---|
| 0 | kBEdx | -15.001348 | -8.276000 | -0.005876 | 3.179103 |
| 1 | 62mP7 | 14.272088 | -3.475083 | 0.999183 | 26.953261 |
| 2 | vyE1P | 6.263187 | -5.948386 | 5.001160 | 134.766305 |
| 3 | KcrkZ | -13.081196 | -11.506057 | 4.999415 | 137.945408 |
| 4 | AHL4O | 12.702195 | -8.147433 | 5.004363 | 134.766305 |
# версия для ревьюера
geo_data_2 = pd.read_csv('/datasets/geo_data_2.csv')
# моя локальная версия
# geo_data_2 = pd.read_csv('geo_data_2.csv')
geo_data_2.head()
| id | f0 | f1 | f2 | product | |
|---|---|---|---|---|---|
| 0 | fwXo0 | -1.146987 | 0.963328 | -0.828965 | 27.758673 |
| 1 | WJtFt | 0.262778 | 0.269839 | -2.530187 | 56.069697 |
| 2 | ovLUW | 0.194587 | 0.289035 | -5.586433 | 62.871910 |
| 3 | q6cA6 | 2.236060 | -0.553760 | 0.930038 | 114.572842 |
| 4 | WPMUX | -0.515993 | 1.716266 | 5.899011 | 149.600746 |
Вывод: данные загрузились корректно.
Функция для изучения данных.
# Напишем фунцию для получения информации о данных
def data_frame_info (data_frame):
display(data_frame.head())
display('-'* 25)
list_c = data_frame.columns
display('Количество явных дубликатов:', data_frame.duplicated().sum())
display('-'* 25)
display(data_frame.info())
display('-'* 25)
display('Гистограммы для данных в столбцах')
display(data_frame.hist(figsize=(30, 20)))
plt.show()
display('-'* 25)
display(data_frame.corr())
display('-'* 25)
display('Попарные графики распределения')
pd.plotting.scatter_matrix(data_frame, figsize=(30, 20))
plt.show()
for col_l in list_c:
display('-'* 25)
display('Информация для колонки', col_l)
display(data_frame[col_l].describe())
display('-'* 25)
display('Уникальные значения:')
display(col_l, data_frame[col_l].sort_values().unique())
display('-'* 25)
display('Колличество каждого уникального значения:')
display(data_frame[col_l].value_counts(sort=True, ascending=False))
display('-'* 25)
display('Пропуски:')
display(col_l,': кол-во NaN',data_frame[col_l].isna().sum(),
', процент NaN', round(data_frame[col_l].isna().sum()/len(data_frame)*100, 2),'%')
data_frame_info(geo_data_0)
| id | f0 | f1 | f2 | product | |
|---|---|---|---|---|---|
| 0 | txEyH | 0.705745 | -0.497823 | 1.221170 | 105.280062 |
| 1 | 2acmU | 1.334711 | -0.340164 | 4.365080 | 73.037750 |
| 2 | 409Wp | 1.022732 | 0.151990 | 1.419926 | 85.265647 |
| 3 | iJLyR | -0.032172 | 0.139033 | 2.978566 | 168.620776 |
| 4 | Xdl7t | 1.988431 | 0.155413 | 4.751769 | 154.036647 |
'-------------------------'
'Количество явных дубликатов:'
0
'-------------------------'
<class 'pandas.core.frame.DataFrame'> RangeIndex: 100000 entries, 0 to 99999 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 100000 non-null object 1 f0 100000 non-null float64 2 f1 100000 non-null float64 3 f2 100000 non-null float64 4 product 100000 non-null float64 dtypes: float64(4), object(1) memory usage: 3.8+ MB
None
'-------------------------'
'Гистограммы для данных в столбцах'
array([[<AxesSubplot:title={'center':'f0'}>,
<AxesSubplot:title={'center':'f1'}>],
[<AxesSubplot:title={'center':'f2'}>,
<AxesSubplot:title={'center':'product'}>]], dtype=object)
'-------------------------'
| f0 | f1 | f2 | product | |
|---|---|---|---|---|
| f0 | 1.000000 | -0.440723 | -0.003153 | 0.143536 |
| f1 | -0.440723 | 1.000000 | 0.001724 | -0.192356 |
| f2 | -0.003153 | 0.001724 | 1.000000 | 0.483663 |
| product | 0.143536 | -0.192356 | 0.483663 | 1.000000 |
'-------------------------'
'Попарные графики распределения'
'-------------------------'
'Информация для колонки'
'id'
count 100000 unique 99990 top TtcGQ freq 2 Name: id, dtype: object
'-------------------------'
'Уникальные значения:'
'id'
array(['006OJ', '009eY', '00AfQ', ..., 'zztWK', 'zzyhQ', 'zzzLH'],
dtype=object)
'-------------------------'
'Колличество каждого уникального значения:'
TtcGQ 2
bsk9y 2
QcMuo 2
fiKDv 2
74z30 2
..
oQiXF 1
kzP5Y 1
Kr4PA 1
UC8oJ 1
TTOBZ 1
Name: id, Length: 99990, dtype: int64
'-------------------------'
'Пропуски:'
'id'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'f0'
count 100000.000000 mean 0.500419 std 0.871832 min -1.408605 25% -0.072580 50% 0.502360 75% 1.073581 max 2.362331 Name: f0, dtype: float64
'-------------------------'
'Уникальные значения:'
'f0'
array([-1.40860531, -1.35177299, -1.30222711, ..., 2.33375269,
2.33707957, 2.36233081])
'-------------------------'
'Колличество каждого уникального значения:'
-1.218900 1
0.407085 1
1.162604 1
0.886105 1
0.031749 1
..
0.425951 1
0.900468 1
0.865040 1
-0.126201 1
1.751793 1
Name: f0, Length: 100000, dtype: int64
'-------------------------'
'Пропуски:'
'f0'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'f1'
count 100000.000000 mean 0.250143 std 0.504433 min -0.848218 25% -0.200881 50% 0.250252 75% 0.700646 max 1.343769 Name: f1, dtype: float64
'-------------------------'
'Уникальные значения:'
'f1'
array([-0.8482185 , -0.84490792, -0.8205609 , ..., 1.33334561,
1.33482762, 1.34376933])
'-------------------------'
'Колличество каждого уникального значения:'
0.296390 1
-0.360328 1
-0.203243 1
0.040378 1
0.860821 1
..
0.130016 1
0.105239 1
-0.007497 1
1.086693 1
1.010051 1
Name: f1, Length: 100000, dtype: int64
'-------------------------'
'Пропуски:'
'f1'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'f2'
count 100000.000000 mean 2.502647 std 3.248248 min -12.088328 25% 0.287748 50% 2.515969 75% 4.715088 max 16.003790 Name: f2, dtype: float64
'-------------------------'
'Уникальные значения:'
'f2'
array([-12.08832812, -10.13834135, -10.13817115, ..., 15.23032159,
15.42837187, 16.00379001])
'-------------------------'
'Колличество каждого уникального значения:'
4.623968 1
0.485455 1
0.899164 1
5.973383 1
4.349341 1
..
5.279791 1
0.796217 1
-1.651532 1
7.610327 1
4.001244 1
Name: f2, Length: 100000, dtype: int64
'-------------------------'
'Пропуски:'
'f2'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'product'
count 100000.000000 mean 92.500000 std 44.288691 min 0.000000 25% 56.497507 50% 91.849972 75% 128.564089 max 185.364347 Name: product, dtype: float64
'-------------------------'
'Уникальные значения:'
'product'
array([0.00000000e+00, 4.02152316e-03, 6.11363631e-03, ...,
1.85355615e+02, 1.85362690e+02, 1.85364347e+02])
'-------------------------'
'Колличество каждого уникального значения:'
0.000000 1
30.141653 1
56.573577 1
50.182301 1
88.989990 1
..
146.560758 1
155.729658 1
27.829511 1
85.382946 1
154.424007 1
Name: product, Length: 100000, dtype: int64
'-------------------------'
'Пропуски:'
'product'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
Выводы:
f0 и f1 очень красивые. Все же корреляции в пределах слабых.id. Посмотрим на них.doble_id_list_geo_0 = geo_data_0.value_counts('id').head(10).index.to_list()
doble_id_list_geo_0
['Tdehs', 'bxg6G', 'QcMuo', 'HZww2', 'TtcGQ', 'AGS9W', 'fiKDv', 'bsk9y', 'A5aEY', '74z30']
geo_data_0.query('id in @doble_id_list_geo_0').sort_values(by='id')
| id | f0 | f1 | f2 | product | |
|---|---|---|---|---|---|
| 66136 | 74z30 | 1.084962 | -0.312358 | 6.990771 | 127.643327 |
| 64022 | 74z30 | 0.741456 | 0.459229 | 5.153109 | 140.771492 |
| 51970 | A5aEY | -0.180335 | 0.935548 | -2.094773 | 33.020205 |
| 3389 | A5aEY | -0.039949 | 0.156872 | 0.209861 | 89.249364 |
| 69163 | AGS9W | -0.933795 | 0.116194 | -3.655896 | 19.230453 |
| 42529 | AGS9W | 1.454747 | -0.479651 | 0.683380 | 126.370504 |
| 931 | HZww2 | 0.755284 | 0.368511 | 1.863211 | 30.681774 |
| 7530 | HZww2 | 1.061194 | -0.373969 | 10.430210 | 158.828695 |
| 63593 | QcMuo | 0.635635 | -0.473422 | 0.862670 | 64.578675 |
| 1949 | QcMuo | 0.506563 | -0.323775 | -2.215583 | 75.496502 |
| 75715 | Tdehs | 0.112079 | 0.430296 | 3.218993 | 60.964018 |
| 21426 | Tdehs | 0.829407 | 0.298807 | -0.049563 | 96.035308 |
| 92341 | TtcGQ | 0.110711 | 1.022689 | 0.911381 | 101.318008 |
| 60140 | TtcGQ | 0.569276 | -0.104876 | 6.440215 | 85.350186 |
| 89582 | bsk9y | 0.398908 | -0.400253 | 10.122376 | 163.433078 |
| 97785 | bsk9y | 0.378429 | 0.005837 | 0.160827 | 160.637302 |
| 41724 | bxg6G | -0.823752 | 0.546319 | 3.630479 | 93.007798 |
| 1364 | bxg6G | 0.411645 | 0.856830 | -3.653440 | 73.604260 |
| 16633 | fiKDv | 0.157341 | 1.028359 | 5.585586 | 95.817889 |
| 90815 | fiKDv | 0.049883 | 0.841313 | 6.394613 | 137.346586 |
К сожалению, без помощи специалиста, мы не можем проверить достоверность данных. В нашем случе мы можем легко удалить эти данные, так как 20 строк из 10000 строк - это очень мало. Сделаем это в следующем разделе.
data_frame_info(geo_data_1)
| id | f0 | f1 | f2 | product | |
|---|---|---|---|---|---|
| 0 | kBEdx | -15.001348 | -8.276000 | -0.005876 | 3.179103 |
| 1 | 62mP7 | 14.272088 | -3.475083 | 0.999183 | 26.953261 |
| 2 | vyE1P | 6.263187 | -5.948386 | 5.001160 | 134.766305 |
| 3 | KcrkZ | -13.081196 | -11.506057 | 4.999415 | 137.945408 |
| 4 | AHL4O | 12.702195 | -8.147433 | 5.004363 | 134.766305 |
'-------------------------'
'Количество явных дубликатов:'
0
'-------------------------'
<class 'pandas.core.frame.DataFrame'> RangeIndex: 100000 entries, 0 to 99999 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 100000 non-null object 1 f0 100000 non-null float64 2 f1 100000 non-null float64 3 f2 100000 non-null float64 4 product 100000 non-null float64 dtypes: float64(4), object(1) memory usage: 3.8+ MB
None
'-------------------------'
'Гистограммы для данных в столбцах'
array([[<AxesSubplot:title={'center':'f0'}>,
<AxesSubplot:title={'center':'f1'}>],
[<AxesSubplot:title={'center':'f2'}>,
<AxesSubplot:title={'center':'product'}>]], dtype=object)
'-------------------------'
| f0 | f1 | f2 | product | |
|---|---|---|---|---|
| f0 | 1.000000 | 0.182287 | -0.001777 | -0.030491 |
| f1 | 0.182287 | 1.000000 | -0.002595 | -0.010155 |
| f2 | -0.001777 | -0.002595 | 1.000000 | 0.999397 |
| product | -0.030491 | -0.010155 | 0.999397 | 1.000000 |
'-------------------------'
'Попарные графики распределения'
'-------------------------'
'Информация для колонки'
'id'
count 100000 unique 99996 top wt4Uk freq 2 Name: id, dtype: object
'-------------------------'
'Уникальные значения:'
'id'
array(['0022J', '003Gl', '003Vx', ..., 'zzv4E', 'zzy2c', 'zzzvI'],
dtype=object)
'-------------------------'
'Колличество каждого уникального значения:'
wt4Uk 2
5ltQ6 2
bfPNe 2
LHZR0 2
Nr86u 1
..
MBFKV 1
Ld5Kb 1
ZenRa 1
7YJT4 1
a6PUy 1
Name: id, Length: 99996, dtype: int64
'-------------------------'
'Пропуски:'
'id'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'f0'
count 100000.000000 mean 1.141296 std 8.965932 min -31.609576 25% -6.298551 50% 1.153055 75% 8.621015 max 29.421755 Name: f0, dtype: float64
'-------------------------'
'Уникальные значения:'
'f0'
array([-31.60957602, -27.82961614, -26.64625507, ..., 28.93082879,
29.25906208, 29.42175461])
'-------------------------'
'Колличество каждого уникального значения:'
9.471578 1
17.560369 1
20.515576 1
4.648343 1
8.195679 1
..
-8.003850 1
14.444814 1
-9.439604 1
4.966853 1
2.442558 1
Name: f0, Length: 100000, dtype: int64
'-------------------------'
'Пропуски:'
'f0'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'f1'
count 100000.000000 mean -4.796579 std 5.119872 min -26.358598 25% -8.267985 50% -4.813172 75% -1.332816 max 18.734063 Name: f1, dtype: float64
'-------------------------'
'Уникальные значения:'
'f1'
array([-26.35859801, -25.38962242, -25.2915177 , ..., 16.0268693 ,
16.7371962 , 18.73406263])
'-------------------------'
'Колличество каждого уникального значения:'
-7.148426 1
-6.241057 1
0.219865 1
-1.778543 1
-9.636482 1
..
-9.680052 1
-7.416345 1
-7.319855 1
-10.020829 1
-0.688613 1
Name: f1, Length: 100000, dtype: int64
'-------------------------'
'Пропуски:'
'f1'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'f2'
count 100000.000000 mean 2.494541 std 1.703572 min -0.018144 25% 1.000021 50% 2.011479 75% 3.999904 max 5.019721 Name: f2, dtype: float64
'-------------------------'
'Уникальные значения:'
'f2'
array([-0.01814409, -0.01788668, -0.01768626, ..., 5.01750345,
5.01909142, 5.01972056])
'-------------------------'
'Колличество каждого уникального значения:'
3.996243 1
4.003988 1
1.996189 1
0.996936 1
3.998589 1
..
1.004759 1
4.997377 1
3.003466 1
0.001041 1
4.992682 1
Name: f2, Length: 100000, dtype: int64
'-------------------------'
'Пропуски:'
'f2'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'product'
count 100000.000000 mean 68.825000 std 45.944423 min 0.000000 25% 26.953261 50% 57.085625 75% 107.813044 max 137.945408 Name: product, dtype: float64
'-------------------------'
'Уникальные значения:'
'product'
array([ 0. , 3.17910258, 26.95326103, 30.13236361,
53.90652206, 57.08562465, 80.85978309, 84.03888568,
107.81304413, 110.99214671, 134.76630516, 137.94540774])
'-------------------------'
'Колличество каждого уникального значения:'
53.906522 8472 26.953261 8468 84.038886 8431 57.085625 8390 3.179103 8337 80.859783 8320 30.132364 8306 134.766305 8304 110.992147 8303 0.000000 8235 137.945408 8233 107.813044 8201 Name: product, dtype: int64
'-------------------------'
'Пропуски:'
'product'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
Выводы: В geo_data_1 обнаружены следующие особенности:
id. Мы удалим строки с не уникальными id в следующем разделе.f2 и prodict. Приэтом зависимоть от f0 и f1 крайне мала. Это плохо, так как может привести к утечке данных о целевом признаке.product имеет всего 12 уникальных значений, количество которых очень близко.Конечно, мы предупреждены о синтетичности данных, но в реальности, нужно было бы провести переговоры с заказчиком о корректности данных, а без них не брать результаты в расчет. Мы оставим эти данные для "тренировки", но не будем забывать от "утечке данных о целевом признаке".
# запишем не уникальные id
doble_id_list_geo_1 = geo_data_1.value_counts('id').head(4).index.to_list()
doble_id_list_geo_1
['wt4Uk', 'LHZR0', '5ltQ6', 'bfPNe']
Дополнительно: Скорее всего наблюдать за работой в регионе geo_1 будет достаточно скучно при такой сильной утечки из product в f2, поэтому создадим еще один регион geo_3, который быдет как geo_1 только без f2. Сделаем это ниже.
data_frame_info(geo_data_2)
| id | f0 | f1 | f2 | product | |
|---|---|---|---|---|---|
| 0 | fwXo0 | -1.146987 | 0.963328 | -0.828965 | 27.758673 |
| 1 | WJtFt | 0.262778 | 0.269839 | -2.530187 | 56.069697 |
| 2 | ovLUW | 0.194587 | 0.289035 | -5.586433 | 62.871910 |
| 3 | q6cA6 | 2.236060 | -0.553760 | 0.930038 | 114.572842 |
| 4 | WPMUX | -0.515993 | 1.716266 | 5.899011 | 149.600746 |
'-------------------------'
'Количество явных дубликатов:'
0
'-------------------------'
<class 'pandas.core.frame.DataFrame'> RangeIndex: 100000 entries, 0 to 99999 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 100000 non-null object 1 f0 100000 non-null float64 2 f1 100000 non-null float64 3 f2 100000 non-null float64 4 product 100000 non-null float64 dtypes: float64(4), object(1) memory usage: 3.8+ MB
None
'-------------------------'
'Гистограммы для данных в столбцах'
array([[<AxesSubplot:title={'center':'f0'}>,
<AxesSubplot:title={'center':'f1'}>],
[<AxesSubplot:title={'center':'f2'}>,
<AxesSubplot:title={'center':'product'}>]], dtype=object)
'-------------------------'
| f0 | f1 | f2 | product | |
|---|---|---|---|---|
| f0 | 1.000000 | 0.000528 | -0.000448 | -0.001987 |
| f1 | 0.000528 | 1.000000 | 0.000779 | -0.001012 |
| f2 | -0.000448 | 0.000779 | 1.000000 | 0.445871 |
| product | -0.001987 | -0.001012 | 0.445871 | 1.000000 |
'-------------------------'
'Попарные графики распределения'
'-------------------------'
'Информация для колонки'
'id'
count 100000 unique 99996 top KUPhW freq 2 Name: id, dtype: object
'-------------------------'
'Уникальные значения:'
'id'
array(['009Gl', '00AuD', '00CaL', ..., 'zzqqy', 'zzsKd', 'zzz9h'],
dtype=object)
'-------------------------'
'Колличество каждого уникального значения:'
KUPhW 2
VF7Jo 2
xCHr8 2
Vcm5J 2
qmiQ6 1
..
lOvYw 1
3vc8N 1
zEitM 1
45dhj 1
6Mkrc 1
Name: id, Length: 99996, dtype: int64
'-------------------------'
'Пропуски:'
'id'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'f0'
count 100000.000000 mean 0.002023 std 1.732045 min -8.760004 25% -1.162288 50% 0.009424 75% 1.158535 max 7.238262 Name: f0, dtype: float64
'-------------------------'
'Уникальные значения:'
'f0'
array([-8.76000362, -7.45058711, -7.18949804, ..., 7.19461485,
7.21552717, 7.23826248])
'-------------------------'
'Колличество каждого уникального значения:'
1.936057 1
-0.784222 1
-2.351132 1
0.075698 1
2.018140 1
..
1.839556 1
0.754413 1
-0.401612 1
-2.771517 1
-0.107381 1
Name: f0, Length: 100000, dtype: int64
'-------------------------'
'Пропуски:'
'f0'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'f1'
count 100000.000000 mean -0.002081 std 1.730417 min -7.084020 25% -1.174820 50% -0.009482 75% 1.163678 max 7.844801 Name: f1, dtype: float64
'-------------------------'
'Уникальные значения:'
'f1'
array([-7.08401976, -6.74835677, -6.73299712, ..., 7.10161842,
7.76185714, 7.84480127])
'-------------------------'
'Колличество каждого уникального значения:'
-0.424195 1
0.182324 1
0.577029 1
0.162839 1
1.018798 1
..
3.052439 1
-1.156062 1
-1.470669 1
0.889380 1
1.717903 1
Name: f1, Length: 100000, dtype: int64
'-------------------------'
'Пропуски:'
'f1'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'f2'
count 100000.000000 mean 2.495128 std 3.473445 min -11.970335 25% 0.130359 50% 2.484236 75% 4.858794 max 16.739402 Name: f2, dtype: float64
'-------------------------'
'Уникальные значения:'
'f2'
array([-11.97033454, -11.61169048, -11.40724351, ..., 16.31301122,
16.35764509, 16.73940206])
'-------------------------'
'Колличество каждого уникального значения:'
1.553554 1
1.549816 1
10.578156 1
1.456220 1
8.867106 1
..
-0.935811 1
0.516740 1
-1.548120 1
2.036096 1
0.206914 1
Name: f2, Length: 100000, dtype: int64
'-------------------------'
'Пропуски:'
'f2'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колонки'
'product'
count 100000.000000 mean 95.000000 std 44.749921 min 0.000000 25% 59.450441 50% 94.925613 75% 130.595027 max 190.029838 Name: product, dtype: float64
'-------------------------'
'Уникальные значения:'
'product'
array([0.00000000e+00, 4.60600004e-03, 9.20411196e-03, ...,
1.90011722e+02, 1.90013589e+02, 1.90029838e+02])
'-------------------------'
'Колличество каждого уникального значения:'
0.000000 1
53.211205 1
37.811021 1
84.266951 1
38.939298 1
..
122.326832 1
34.629840 1
3.577922 1
0.937771 1
49.640390 1
Name: product, Length: 100000, dtype: int64
'-------------------------'
'Пропуски:'
'product'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
Выводы:
f2 слабая, остальные очень слабая зависимости).id- привычно удалим в следующем разделе.# запишем не уникальные id
doble_id_list_geo_2 = geo_data_2.value_counts('id').head(4).index.to_list()
doble_id_list_geo_2
['xCHr8', 'KUPhW', 'Vcm5J', 'VF7Jo']
Для всех файлов столбец id будет лишним при обучении моделей.
Удалим строки у которых были одинаковые значения в столбце id и сам столбец id.
geo_data_0 = geo_data_0.query('id not in @doble_id_list_geo_0').drop(columns=['id'])
geo_data_0.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 99980 entries, 0 to 99999 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 f0 99980 non-null float64 1 f1 99980 non-null float64 2 f2 99980 non-null float64 3 product 99980 non-null float64 dtypes: float64(4) memory usage: 3.8 MB
Удалим строки у которых были одинаковые значения в столбце id и сам столбец id.
geo_data_1 = geo_data_1.query('id not in @doble_id_list_geo_1').drop(columns=['id'])
geo_data_1.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 99992 entries, 0 to 99999 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 f0 99992 non-null float64 1 f1 99992 non-null float64 2 f2 99992 non-null float64 3 product 99992 non-null float64 dtypes: float64(4) memory usage: 3.8 MB
Удалим строки у которых были одинаковые значения в столбце id и сам столбец id.
geo_data_2 = geo_data_2.query('id not in @doble_id_list_geo_2').drop(columns=['id'])
geo_data_2.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 99992 entries, 0 to 99999 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 f0 99992 non-null float64 1 f1 99992 non-null float64 2 f2 99992 non-null float64 3 product 99992 non-null float64 dtypes: float64(4) memory usage: 3.8 MB
Cоздадим еще один регион geo_3, который быдет как geo_1 только без f2.
geo_data_3 = geo_data_1.copy(deep=True).drop(columns='f2')
geo_data_3.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 99992 entries, 0 to 99999 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 f0 99992 non-null float64 1 f1 99992 non-null float64 2 product 99992 non-null float64 dtypes: float64(3) memory usage: 3.1 MB
В данном проекте мы будем решать задучу регрессии, тоесть результатом работы моделей будет число для каждого объекта.
Для наших целей выделем целевой признак product в target, а остальные признаки в futures. И так для каждого региона: geo_0, geo_1, geo_2.
target_geo_0 = geo_data_0['product']
features_geo_0 = geo_data_0.drop(columns='product')
features_geo_0.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 99980 entries, 0 to 99999 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 f0 99980 non-null float64 1 f1 99980 non-null float64 2 f2 99980 non-null float64 dtypes: float64(3) memory usage: 3.1 MB
target_geo_0.head()
0 105.280062 1 73.037750 2 85.265647 3 168.620776 4 154.036647 Name: product, dtype: float64
target_geo_1 = geo_data_1['product']
features_geo_1 = geo_data_1.drop(columns='product')
target_geo_2 = geo_data_2['product']
features_geo_2 = geo_data_2.drop(columns='product')
target_geo_3 = geo_data_3['product']
features_geo_3 = geo_data_3.drop(columns='product')
В этом проекте для вычеслений и выборок будет использоваться механизм генератора псевдослучайных значений. Для нашего проекта мы выбрали этот параметр random_state равным 54321 для всего проекта.
rs = 54321
Опираясь на условия нашего проекта разделим данные по каждому региону на 2 пары выборок в следующих пропорциях:
features_train, target_trainfeatures_valid, target_validМы будем применить тренировочную пару для обучения моделей.
Проверочную пару для проверки.
features_train_geo_0, features_valid_geo_0, target_train_geo_0, target_valid_geo_0 = train_test_split(
features_geo_0, target_geo_0, test_size=0.25, random_state=rs
)
features_train_geo_0.shape
(74985, 3)
features_train_geo_1, features_valid_geo_1, target_train_geo_1, target_valid_geo_1 = train_test_split(
features_geo_1, target_geo_1, test_size=0.25, random_state=rs
)
features_train_geo_2, features_valid_geo_2, target_train_geo_2, target_valid_geo_2 = train_test_split(
features_geo_2, target_geo_2, test_size=0.25, random_state=rs
)
features_train_geo_3, features_valid_geo_3, target_train_geo_3, target_valid_geo_3 = train_test_split(
features_geo_3, target_geo_3, test_size=0.25, random_state=rs
)
Так как по условиям проекта мы будем использовать модель Линейной регрессии, то мы можем заренее помочь моделям тем, что произведем маштабирование данных для каждого региона.
Одним из методов маштабирования является стандартизация данных. При ее использовании мы получим все признаки со средними равными 0 и дисперсией равной 1. Но это на обучающей выборке. На остальных выборках, возможны не большие отклонения.
# создадим и обучим модель, которая сделает для нас стандартизацю, на учебной выборке
scaler = StandardScaler()
scaler.fit(features_train_geo_0)
# стандартизируем наши выборки
features_train_geo_0 = scaler.transform(features_train_geo_0)
features_valid_geo_0 = scaler.transform(features_valid_geo_0)
# создадим и обучим модель, которая сделает для нас стандартизацю, на учебной выборке
scaler = StandardScaler()
scaler.fit(features_train_geo_1)
# стандартизируем наши выборки
features_train_geo_1 = scaler.transform(features_train_geo_1)
features_valid_geo_1 = scaler.transform(features_valid_geo_1)
# создадим и обучим модель, которая сделает для нас стандартизацю, на учебной выборке
scaler = StandardScaler()
scaler.fit(features_train_geo_2)
# стандартизируем наши выборки
features_train_geo_2 = scaler.transform(features_train_geo_2)
features_valid_geo_2 = scaler.transform(features_valid_geo_2)
# создадим и обучим модель, которая сделает для нас стандартизацю, на учебной выборке
scaler = StandardScaler()
scaler.fit(features_train_geo_3)
# стандартизируем наши выборки
features_train_geo_3 = scaler.transform(features_train_geo_3)
features_valid_geo_3 = scaler.transform(features_valid_geo_3)
Для исследования мы должны выбрать метрику или метрики, которые будет определять качество модели.
Для этого проекта главной метрикой будет являться RMSE - корень квадратный от среднеквадратичной ошибки.
Напишем функцию для её расчета.
def rmse_score (model, features, target):
# получаем предсказания модели
predictions = model.predict(features)
# считаем MSE
mse = mean_squared_error(target, predictions)
# считаем RMSE
rmse = mse ** 0.5
return rmse
Для интерпритации результатов работы моделей нам нужна точка отсчета. Это будет значение RMSE к среднему значению целевого признака. Подготовим наши точки отсчета для каждого региона.
для target_valid_geo_0
etalon_prediction_valid_geo_0 = pd.Series(target_valid_geo_0.mean(), index=target_valid_geo_0.index)
etalon_rmse_valid_geo_0 = mean_squared_error(target_valid_geo_0, etalon_prediction_valid_geo_0) ** 0.5
etalon_rmse_valid_geo_0
44.39299456338535
для target_valid_geo_1
etalon_prediction_valid_geo_1 = pd.Series(target_valid_geo_1.mean(), index=target_valid_geo_1.index)
etalon_rmse_valid_geo_1 = mean_squared_error(target_valid_geo_1, etalon_prediction_valid_geo_1) ** 0.5
etalon_rmse_valid_geo_1
46.00468884959666
для target_valid_geo_2
etalon_prediction_valid_geo_2 = pd.Series(target_valid_geo_2.mean(), index=target_valid_geo_2.index)
etalon_rmse_valid_geo_2 = mean_squared_error(target_valid_geo_2, etalon_prediction_valid_geo_2) ** 0.5
etalon_rmse_valid_geo_2
44.72032387036749
для target_valid_geo_3
etalon_rmse_valid_geo_3 = etalon_rmse_valid_geo_1
У нас всего 3 параметра и всех известных сейчас моделей наиболее не склонной к переобучению будут модели Линейной Регрессии. Их мы и будем использовать.
Создадим и обучим модели для каждого региона на настройках по умолчанию. Не будем забывать, что наши данные уже стандартизоированы.
model = LinearRegression()
model.fit(features_train_geo_0, target_train_geo_0)
display('Регион geo_0. Для средней RMSE = {:.5f} | Для этой модели RMSE = {:.5f}'
.format(etalon_rmse_valid_geo_0, rmse_score(model
, features_valid_geo_0
, target_valid_geo_0)))
'Регион geo_0. Для средней RMSE = 44.39299 | Для этой модели RMSE = 37.81704'
Посмотрим на коэффициенты и отступ.
model.coef_
array([ 3.31409905, -7.03420411, 21.45986038])
model.intercept_
92.66912013816938
model = LinearRegression()
model.fit(features_train_geo_1, target_train_geo_1)
display('Регион geo_1. Для средней RMSE = {:.5f} | Для этой модели RMSE = {:.5f}'
.format(etalon_rmse_valid_geo_1, rmse_score(model
, features_valid_geo_1
, target_valid_geo_1)))
'Регион geo_1. Для средней RMSE = 46.00469 | Для этой модели RMSE = 0.89263'
Если бы не обнаруженная утечка целевого признака ранее мы бы, либо праздновали, либо заподозрили это сейчас. Посмотрим на коэффициенты и отступ.
model.coef_
array([-1.29964181, -0.11127625, 45.89720291])
model.intercept_
68.89188185258926
Вывод:
f2. В частности, мы видим, что коэффициент у этого признака на 1-2 порядка больше чем у других признаков. Наблюдаем дальше.# features_valid_geo_1 - проверочная выборка
best_model_geo_1 = model
model = LinearRegression()
model.fit(features_train_geo_2, target_train_geo_2)
# rmse_score(model, features_valid_geo_2, target_valid_geo_2)
display('Регион geo_2. Для средней RMSE = {:.5f} | Для этой модели RMSE = {:.5f}'
.format(etalon_rmse_valid_geo_2, rmse_score(model
, features_valid_geo_2
, target_valid_geo_2)))
'Регион geo_2. Для средней RMSE = 44.72032 | Для этой модели RMSE = 40.02966'
Посмотрим на коэффициенты и отступ.
model.coef_
array([-0.19676658, -0.15932639, 19.95452868])
model.intercept_
95.0241866799295
model = LinearRegression()
model.fit(features_train_geo_3, target_train_geo_3)
# rmse_score(model, features_valid_geo_3, target_valid_geo_3)
display('Регион geo_3. Для средней RMSE = {:.5f} | Для этой модели RMSE = {:.5f}'
.format(etalon_rmse_valid_geo_3, rmse_score(model
, features_valid_geo_3
, target_valid_geo_3)))
'Регион geo_3. Для средней RMSE = 46.00469 | Для этой модели RMSE = 45.97543'
Здесь ситауция не такая жуликоватая как в geo_1. А больше похожа на geo_0 и geo_2.
model.coef_
array([-1.23571289, -0.28865235])
model.intercept_
68.89188185258926
Вывод:
f2. Поэтому мы создали geo_3. Его результаты больше похожи на остальные регионы.Постараемся улучшить значение RMSE наших моделей за счет работы с данными, а именно увеличим колличество признаков за счет перемножения признаков до различных степеней.
stat_geo_0 = pd.DataFrame(columns=['degree', 'rmse'])
#Цикл по степеням от 2 до 10
for degree in range(2, 11):
# создадим и обучим модель для преобразования наших данных
poly = PolynomialFeatures(degree=degree, interaction_only=False, include_bias=False, order='C')
poly.fit(features_train_geo_0)
# создадим обучающую и проверяющие выборки
features_train_geo_0_poly = poly.transform(features_train_geo_0)
features_valid_geo_0_poly = poly.transform(features_valid_geo_0)
# создадим и обучим модель линейной регрессии на подготовленной обучающей выборке
model = LinearRegression()
model.fit(features_train_geo_0_poly, target_train_geo_0)
# выведем результаты
stat_geo_0 = stat_geo_0.append({'degree': degree
, 'rmse': rmse_score(model, features_valid_geo_0_poly, target_valid_geo_0)}
, ignore_index=True
)
stat_geo_0
| degree | rmse | |
|---|---|---|
| 0 | 2.0 | 37.816963 |
| 1 | 3.0 | 37.430780 |
| 2 | 4.0 | 37.435561 |
| 3 | 5.0 | 37.246498 |
| 4 | 6.0 | 37.250935 |
| 5 | 7.0 | 37.220042 |
| 6 | 8.0 | 37.231947 |
| 7 | 9.0 | 37.233597 |
| 8 | 10.0 | 37.255279 |
stat_geo_0.plot(x='degree'
, y='rmse'
, kind='line'
, xlabel='Degree'
, ylabel='RMSE'
, title='График зависимости RMSE от степени признаков в линейной регрессии'
)
plt.show()
Выводы:
# создадим и обучим модель для преобразования наших данных
poly = PolynomialFeatures(degree=5, interaction_only=False, include_bias=False, order='C')
poly.fit(features_train_geo_0)
# создадим обучающую и проверяющие выборки
features_train_geo_0_poly = poly.transform(features_train_geo_0)
features_valid_geo_0_poly = poly.transform(features_valid_geo_0)
# создадим и обучим модель линейной регрессии на подготовленной обучающей выборке. Она будет лучшей.
best_model_geo_0 = LinearRegression()
best_model_geo_0.fit(features_train_geo_0_poly, target_train_geo_0)
best_model_geo_0
LinearRegression()
display('Регион geo_0. Для средней RMSE = {:.5f} | Для этой модели RMSE = {:.5f}'
.format(etalon_rmse_valid_geo_0, rmse_score(best_model_geo_0
, features_valid_geo_0_poly
, target_valid_geo_0)))
'Регион geo_0. Для средней RMSE = 44.39299 | Для этой модели RMSE = 37.24650'
Как много коэффициентов нам понадобилось...
best_model_geo_0.coef_
array([-2.79802179e+01, -1.06050700e+01, 2.48479384e+01, -1.66060236e+00,
-2.84324105e-02, -1.30871724e-01, -2.24483324e-01, -9.91236759e-02,
-3.28815459e-02, 2.54092880e+01, 7.99937878e+00, 1.44123426e-01,
2.06166310e+01, 1.43623324e+00, -4.44506545e-01, 6.24160011e-01,
7.13702790e-01, 2.06160996e-01, -1.54690670e+00, 5.06456068e-01,
-3.60268789e-01, 2.55251744e-01, 1.49648738e-01, 1.58850609e-02,
2.15951071e-02, -9.32705291e-02, -1.00406788e-01, 1.86063343e-01,
-1.14615231e-01, -4.23427677e-02, 8.27992398e-02, 1.57243039e-01,
-6.34137982e-02, 1.18297082e-02, -4.49225553e+00, -1.59640962e+00,
1.09194594e-01, -7.47593934e+00, -1.30743668e-01, 6.34529635e-02,
-8.96368581e-01, -4.41834818e-02, 6.97616101e-02, -5.90221912e-02,
-2.82713455e+00, -5.94138906e-01, 3.89045372e-02, -1.72276310e-01,
2.95453182e-02, 4.01492738e-01, -3.11805827e-01, -1.62593890e-01,
7.54295278e-03, -1.42535246e-02, 5.27238578e-02])
best_model_geo_0.intercept_
93.07738066483167
stat_geo_2 = pd.DataFrame(columns=['degree', 'rmse'])
#Цикл по степеням от 2 до 10
for degree in range(2, 11):
# создадим и обучим модель для преобразования наших данных
poly = PolynomialFeatures(degree=degree, interaction_only=False, include_bias=False, order='C')
poly.fit(features_train_geo_2)
# создадим обучающую и проверяющие выборки
features_train_geo_2_poly = poly.transform(features_train_geo_2)
features_valid_geo_2_poly = poly.transform(features_valid_geo_2)
# создадим и обучим модель линейной регрессии на подготовленной обучающей выборке
model = LinearRegression()
model.fit(features_train_geo_2_poly, target_train_geo_2)
# выведем результаты
stat_geo_2 = stat_geo_2.append({'degree': degree
, 'rmse': rmse_score(model, features_valid_geo_2_poly, target_valid_geo_2)}
, ignore_index=True
)
stat_geo_2
| degree | rmse | |
|---|---|---|
| 0 | 2.0 | 38.728291 |
| 1 | 3.0 | 38.683968 |
| 2 | 4.0 | 38.070445 |
| 3 | 5.0 | 38.096281 |
| 4 | 6.0 | 37.873698 |
| 5 | 7.0 | 37.871871 |
| 6 | 8.0 | 37.917255 |
| 7 | 9.0 | 37.917082 |
| 8 | 10.0 | 38.455086 |
stat_geo_2.plot(x='degree'
, y='rmse'
, kind='line'
, xlabel='Degree'
, ylabel='RMSE'
, title='График зависимости RMSE от степени признаков в линейной регрессии'
)
plt.show()
Выводы:
f2 помогла другим признакам стать более значимыми. И для этого пришлось не мало потрудиться.# создадим и обучим модель для преобразования наших данных
poly = PolynomialFeatures(degree=6, interaction_only=False, include_bias=False, order='C')
poly.fit(features_train_geo_2)
# создадим обучающую и проверяющие выборки
features_train_geo_2_poly = poly.transform(features_train_geo_2)
features_valid_geo_2_poly = poly.transform(features_valid_geo_2)
# создадим и обучим модель линейной регрессии на подготовленной обучающей выборке. Она будет лучшей.
best_model_geo_2 = LinearRegression()
best_model_geo_2.fit(features_train_geo_2_poly, target_train_geo_2)
best_model_geo_2
LinearRegression()
display('Регион geo_2. Для средней RMSE = {:.5f} | Для этой модели RMSE = {:.5f}'
.format(etalon_rmse_valid_geo_2, rmse_score(best_model_geo_2
, features_valid_geo_2_poly
, target_valid_geo_2)))
'Регион geo_2. Для средней RMSE = 44.72032 | Для этой модели RMSE = 37.87370'
Как много коэффициентов нам понадобилось...
best_model_geo_2.coef_
array([-2.35102535e-01, -5.16676021e-01, 2.31025233e+01, 1.77911743e+01,
4.64046545e-01, 3.94543128e-01, 1.81568828e+01, -9.53427578e-02,
1.47130854e-01, -1.13483465e-01, 2.86918664e-01, -8.87729944e-02,
2.08563745e-01, -4.05092280e-01, -5.12184667e-02, 1.93088886e-01,
3.79375116e-01, 1.65962441e-01, -1.32051766e+00, -2.47834319e+00,
-7.33060021e-01, -8.34852873e-02, -4.89871990e+00, 6.17802884e-01,
1.03469635e-01, 2.94857286e-02, -1.85366052e-01, 3.15769284e-01,
-7.53992917e-02, -2.55109098e+00, 9.33084411e-03, 1.25803899e-01,
-4.83725147e-02, -8.54440887e-02, 2.05903054e-02, -5.61990406e-02,
-6.74018102e-03, -1.30291196e-02, 6.26889214e-02, 5.20119825e-03,
-2.45131636e-02, -1.69714917e-01, 3.28673232e-02, 5.90954775e-02,
-1.51951338e-02, -1.07949040e-02, -3.60826439e-02, 3.18172508e-02,
1.81924326e-02, -2.46322223e-02, -3.99470169e-02, -2.88715002e-02,
1.09715358e-02, -2.06073629e-02, 3.83938271e-02, 9.72749139e-02,
8.66879207e-02, 3.42095573e-04, 2.58203180e-01, -6.99648753e-02,
-4.97784050e-03, 5.32289404e-02, 1.12725030e-02, 4.80753196e-03,
3.59545240e-02, 3.26937054e-01, -9.75427074e-03, -6.96594029e-02,
-5.68934106e-02, 1.26832889e-02, -4.77129653e-03, 4.61252838e-04,
-7.31754670e-02, 8.37993963e-02, -2.79060946e-02, -1.43739231e-02,
1.00005627e-01, 7.08351320e-03, -2.46851778e-02, -1.63394671e-02,
4.80387534e-03, 1.12740330e-02, 3.01948784e-03])
best_model_geo_2.intercept_
74.33303484653742
stat_geo_3 = pd.DataFrame(columns=['degree', 'rmse'])
#Цикл по степеням от 2 до 10
for degree in range(2, 11):
# создадим и обучим модель для преобразования наших данных
poly = PolynomialFeatures(degree=degree, interaction_only=False, include_bias=False, order='C')
poly.fit(features_train_geo_3)
# создадим обучающую и проверяющие выборки
features_train_geo_3_poly = poly.transform(features_train_geo_3)
features_valid_geo_3_poly = poly.transform(features_valid_geo_3)
# создадим и обучим модель линейной регрессии на подготовленной обучающей выборке
model = LinearRegression()
model.fit(features_train_geo_3_poly, target_train_geo_3)
# выведем результаты
stat_geo_3 = stat_geo_3.append({'degree': degree
, 'rmse': rmse_score(model, features_valid_geo_3_poly, target_valid_geo_3)}
, ignore_index=True
)
stat_geo_3
| degree | rmse | |
|---|---|---|
| 0 | 2.0 | 45.978662 |
| 1 | 3.0 | 45.976535 |
| 2 | 4.0 | 45.976592 |
| 3 | 5.0 | 45.973084 |
| 4 | 6.0 | 45.974253 |
| 5 | 7.0 | 45.976730 |
| 6 | 8.0 | 45.992613 |
| 7 | 9.0 | 45.989221 |
| 8 | 10.0 | 46.043432 |
stat_geo_3.plot(x='degree'
, y='rmse'
, kind='line'
, xlabel='Degree'
, ylabel='RMSE'
, title='График зависимости RMSE от степени признаков в линейной регрессии'
)
plt.show()
Выводы:
f1 и f2 слишком слаба. Улучшение модели не произошло.Обычна модель на настройках по умолчанию с маштабированными признаками.
best_model_geo_3 = LinearRegression()
best_model_geo_3.fit(features_train_geo_3, target_train_geo_3)
best_model_geo_3
LinearRegression()
display('Регион geo_3. Для средней RMSE = {:.5f} | Для этой модели RMSE = {:.5f}'
.format(etalon_rmse_valid_geo_3, rmse_score(best_model_geo_3
, features_valid_geo_3
, target_valid_geo_3)))
'Регион geo_3. Для средней RMSE = 46.00469 | Для этой модели RMSE = 45.97543'
Коэффициенты.
best_model_geo_3.coef_
array([-1.23571289, -0.28865235])
best_model_geo_3.intercept_
68.89188185258926
Сохраним предсказания модели на проверочной выборке, посмотрим на средние предсказанные запасы
prediction_valid_geo_0 = best_model_geo_0.predict(features_valid_geo_0_poly)
type(prediction_valid_geo_0)
numpy.ndarray
Изменим тип предсказаний и выровняем индексы с целевым признаком
prediction_valid_geo_0 = pd.Series(prediction_valid_geo_0, index=target_valid_geo_0.index)
type(prediction_valid_geo_0)
pandas.core.series.Series
prediction_valid_geo_0.mean()
92.61587544962684
в сравнении с реальными запасами
target_valid_geo_0.mean()
91.9893035012297
на фоне RMSE равном
rmse_valid_geo_0 = mean_squared_error(target_valid_geo_0, prediction_valid_geo_0) ** 0.5
rmse_valid_geo_0
37.24649780889486
У нас хватило вычислительной мощности, чтобы проверить близость значений средних. Но, если у нас будет не столь мощьная техника, то мы сможем проверить, это используя t-test.
Посмотрим на распределение и дисперсию.
prediction_valid_geo_0.hist()
<AxesSubplot:>
target_valid_geo_0.hist()
<AxesSubplot:>
Выдвинем гипотизы и проверим их
Проверка гипотезы: среднее значение запасов в реальных данных отличается от среднего значения в предсказаниях;
H_0: Среднее значение реальных данных(target_valid_geo_0) = среднему значению предсказаний (prediction_valid_geo_0) в регионе geo_0
H_1: Среднее значение реальных данных(target_valid_geo_0) ≠ среднему значению предсказаний (prediction_valid_geo_0) в регионе geo_0
alpha = 0.05
# results = вызов метода для проверки гипотезы
results = st.ttest_ind(target_valid_geo_0, prediction_valid_geo_0, equal_var=True)
# alpha = задайте значение уровня значимости
alpha = 0.05
# вывод значения p-value на экран
display(results.pvalue)
# условный оператор с выводом строки с ответом
if results.pvalue < alpha:
print ('Отвергаем нулевую гипотезу')
else:
print ('Не получилось отвергнуть нулевую гипотезу')
0.05000734530015135
Не получилось отвергнуть нулевую гипотезу
Вывод: А точнее мы не отвергли гипотезу, что средние равны, значит продолжаем считать их равными. Но обратим внимание, что значение p-value находится на границе порога. Видно сказывается более высокая дисперсия признаков в этом регионе. Но порог пройден. Результат зафиксирован.
Сохраним предсказания модели на проверочной выборке, посмотрим на средние предсказанные запасы
prediction_valid_geo_1 = best_model_geo_1.predict(features_valid_geo_1)
type(prediction_valid_geo_1)
numpy.ndarray
Изменим тип предсказаний и выровняем индексы с целевым признаком
prediction_valid_geo_1 = pd.Series(prediction_valid_geo_1, index=target_valid_geo_1.index)
type(prediction_valid_geo_1)
pandas.core.series.Series
prediction_valid_geo_1.mean()
68.62876115134554
в сравнении с реальными запасами
target_valid_geo_1.mean()
68.62094534883911
на фоне RMSE равном
rmse_valid_geo_1 = mean_squared_error(target_valid_geo_1, prediction_valid_geo_1) ** 0.5
rmse_valid_geo_1
0.8926275494139493
У нас хватило вычислительной мощьности, чтобы проверить близость значений средних. Но, если у нас будет не столь мощьная техника, то мы сможем проверить, это используя t-test.
Посмотрим на распределение и дисперсию.
prediction_valid_geo_1.hist()
<AxesSubplot:>
target_valid_geo_1.hist()
<AxesSubplot:>
Выдвинем гипотизы и проверим их
Проверка гипотезы: среднее значение запасов в реальных данных отличается от среднего значения в предсказаниях;
H_0: Среднее значение реальных данных(target_valid_geo_1) = среднему значению предсказаний (prediction_valid_geo_1) в регионе geo_1
H_1: Среднее значение реальных данных(target_valid_geo_1) ≠ среднему значению предсказаний (prediction_valid_geo_1) в регионе geo_1
alpha = 0.05
# results = вызов метода для проверки гипотезы
results = st.ttest_ind(target_valid_geo_1, prediction_valid_geo_1, equal_var=True)
# alpha = задайте значение уровня значимости
alpha = 0.05
# вывод значения p-value на экран
display(results.pvalue)
# условный оператор с выводом строки с ответом
if results.pvalue < alpha:
print ('Отвергаем нулевую гипотезу')
else:
print ('Не получилось отвергнуть нулевую гипотезу')
0.9848443502496
Не получилось отвергнуть нулевую гипотезу
Вывод: А точнее мы не отвергли гипотезу, что средние равны, значит продолжаем считать их равными.
Сохраним предсказания модели на проверочной выборке, посмотрим на средние предсказанные запасы
prediction_valid_geo_2 = best_model_geo_2.predict(features_valid_geo_2_poly)
type(prediction_valid_geo_2)
numpy.ndarray
Изменим тип предсказаний и выровняем индексы с целевым признаком
prediction_valid_geo_2 = pd.Series(prediction_valid_geo_2, index=target_valid_geo_2.index)
type(prediction_valid_geo_2)
pandas.core.series.Series
prediction_valid_geo_2.mean()
95.00169252078685
в сравнении с реальными запасами
target_valid_geo_2.mean()
94.92250743093476
на фоне RMSE равном
rmse_valid_geo_2 = mean_squared_error(target_valid_geo_2, prediction_valid_geo_2) ** 0.5
rmse_valid_geo_2
37.87369797829449
У нас хватило вычислительной мощьности, чтобы проверить близость значений средних. Но, если у нас будет не столь мощьная техника, то мы сможем проверить, это используя t-test.
Посмотрим на распределение и дисперсию.
prediction_valid_geo_2.hist()
<AxesSubplot:>
target_valid_geo_2.hist()
<AxesSubplot:>
Выдвинем гипотизы и проверим их
Проверка гипотезы: среднее значение запасов в реальных данных отличается от среднего значения в предсказаниях;
H_0: Среднее значение реальных данных(target_valid_geo_2) = среднему значению предсказаний (prediction_valid_geo_2) в регионе geo_2
H_1: Среднее значение реальных данных(target_valid_geo_2) ≠ среднему значению предсказаний (prediction_valid_geo_2) в регионе geo_2
alpha = 0.05
# results = вызов метода для проверки гипотезы
results = st.ttest_ind(target_valid_geo_2, prediction_valid_geo_2, equal_var=True)
# alpha = задайте значение уровня значимости
alpha = 0.05
# вывод значения p-value на экран
display(results.pvalue)
# условный оператор с выводом строки с ответом
if results.pvalue < alpha:
print ('Отвергаем нулевую гипотезу')
else:
print ('Не получилось отвергнуть нулевую гипотезу')
0.8042950276579385
Не получилось отвергнуть нулевую гипотезу
Вывод: А точнее мы не отвергли гипотезу, что средние равны, значит продолжаем считать их равными.
Сохраним предсказания модели на проверочной выборке, посмотрим на средние предсказанные запасы
prediction_valid_geo_3 = best_model_geo_3.predict(features_valid_geo_3)
type(prediction_valid_geo_3)
numpy.ndarray
Изменим тип предсказаний и выровняем индексы с целевым признаком
prediction_valid_geo_3 = pd.Series(prediction_valid_geo_3, index=target_valid_geo_3.index)
type(prediction_valid_geo_3)
pandas.core.series.Series
prediction_valid_geo_3.mean()
68.89663241991784
в сравнении с реальными запасами
target_valid_geo_3.mean()
68.62094534883911
на фоне RMSE равном
rmse_valid_geo_1 = mean_squared_error(target_valid_geo_3, prediction_valid_geo_3) ** 0.5
rmse_valid_geo_1
45.97542848432249
У нас хватило вычислительной мощьности, чтобы проверить близость значений средних. Но, если у нас будет не столь мощьная техника, то мы сможем проверить, это используя t-test.
Посмотрим на распределение и дисперсию.
prediction_valid_geo_3.hist()
<AxesSubplot:>
target_valid_geo_3.hist()
<AxesSubplot:>
Выдвинем гипотизы и проверим их
Проверка гипотезы: среднее значение запасов в реальных данных отличается от среднего значения в предсказаниях;
H_0: Среднее значение реальных данных(target_valid_geo_3) = среднему значению предсказаний (prediction_valid_geo_3) в регионе geo_3
H_1: Среднее значение реальных данных(target_valid_geo_3) ≠ среднему значению предсказаний (prediction_valid_geo_3) в регионе geo_3
alpha = 0.05
# results = вызов метода для проверки гипотезы
results = st.ttest_ind(target_valid_geo_3, prediction_valid_geo_3, equal_var=True)
# alpha = задайте значение уровня значимости
alpha = 0.05
# вывод значения p-value на экран
display(results.pvalue)
# условный оператор с выводом строки с ответом
if results.pvalue < alpha:
print ('Отвергаем нулевую гипотезу')
else:
print ('Не получилось отвергнуть нулевую гипотезу')
0.3436101443878137
Не получилось отвергнуть нулевую гипотезу
Вывод: А точнее мы не отвергли гипотезу, что средние равны, значит продолжаем считать их равными.
geo_1 как были жуликами были так и остались: результаты предсказаний как под линеечку. Но мы за этим только наблюдаем.geo_3 было призвано для спасения ситуации с geo_1, но модель работает на грани адекватности, оставим для наблюдений. RMSE около 46 тысяч барелей при средней около 69. Вызывает сомнения. Но средние предсказаний и на проверочной выборке в пределах 1%.geo_0 и geo_2 получили модели, которые дали отклонения в пределах 1% для реальной средней проверочной выборки и предсказанной, при том, что RMSE у них значительные 37-38 тысяч барелей, что больше трети от среднего значения.Для дальнейших расчётов предположим, что предсказания сделаные моделями на проверочных выборках, это результаты геологоразведовательных работ в будующем по каждому региону. Как показано выше, эти данные характерны для каждого региона и наше предположение позволит сделать достаточно правдоподобные расчеты далее в проекте.
Так как предсказанных скважин по каждому региону около 25000, то по условиям задачи, мы можем брать случайным образом 500 скважин. Это будут исследованные скважины в будущем. Из них брать лучшие 200 скважин. Это будут разработанные скважины в будующем. А вот экономические показатели будем считать по целевому признаку этих скважин, чтобы они были более реальны.
Для расчета риска и экономических показателей мы можем повторить эту процедуру 1000 раз.
И вычеслять риски по 1000 значений экономических показателей, a после выберем регион, для гипотетически максимально эффективного использования бюджета.
Основным экономическим показателем будет валовая прибыль.
ITERATION_NUMBER = 1000
Количество исследуемых скважин
RESEARCH_WELL = 500
Количество скважин для разработки.
DEVELOPMENT_WELL = 200
Бюджет на разработку 200 скважин. Бюджет на исследование 500 точек (скважин) нам не известен и в денежных расчетах участвовать не будет.
BUDGET = 10_000_000_000
Доход с тысячи барелей.
REVENUE_1000_BARRELS = 450_000
Для расчетов финансовой эффективности мы будем использовать
валовую прибыль = доход - себестоимость.
В терминах проекта
доход = доход от каждой скважины X количество разрабатываемых скважин
себестоимость = бюджет(BUDGET) = средняя себестоимость каждой скважины X количество разрабатываемых скважин(DEVELOPMENT_WELL)
доход от каждой скважины = доход с тысячи барелей(REVENUE_1000_BARRELS) X объем добычи (product)
средняя себестоимость каждой скважины = бюджет(BUDGET) / количество разрабатываемых скважин(DEVELOPMENT_WELL)
валовая прибыль скважины = доход от этой скважины - средняя себестоимость = доход с тысячи барелей(REVENUE_1000_BARRELS) X объем добычи (product) - бюджет(BUDGET) / количество разрабатываемых скважин(DEVELOPMENT_WELL)
валовая прибыль = доход от разрабатываемых скважин - себестоимость(BUDGET) = сумма объемов добычи(product) со всех разрабатываемых скважина(DEVELOPMENT_WELL) X доход с тысячи барелей(REVENUE_1000_BARRELS) - себестоимость(BUDGET)
Средняя себестоимость каждой скважины.
avr_costs_well = BUDGET / DEVELOPMENT_WELL
avr_costs_well
50000000.0
Сколько в среднем должен быть минимальный запас нефти в скважине для положительно валовой прибыли этой скважины. Больше или равно...
avr_costs_well / REVENUE_1000_BARRELS
111.11111111111111
BUDGET / REVENUE_1000_BARRELS
22222.222222222223
Выводы: значение среднего запаса в скважине больше среднего для каждого из регионов. Но не стоит опускать руки, так как это совсем не значит, что иследовав 500 скважин мы не выберем 200 для разработки, у которых средний запас будет больше 111,1... тысяч барелей. И так за дело: Готовим функцию для расчета валовой прибыли и приступаем к расчетам.
Подготовим функцию для расчета валовой прибыли с разрабатываемых скважин.
def gross_profit(development_well_200):
gross_profit_200 = development_well_200.sum() * REVENUE_1000_BARRELS - BUDGET
return gross_profit_200
Не все наши данные имеют нормальное распределение, поэтому мы применим технику Bootstrap. Будем надеятся, что во всех регионах шнурки достаточно крепкие.
Напишем функцию, которая будет расчитывать валовоую прибыль с 200 лучших скважин 1000 раз для из различных выборок по 500 скважин из наших предсказаний. Результатом будет средняя прибыль и 95% доверительный интервал.
# создаем значение для генерации различных псевдослучайных выборок
state = np.random.RandomState(rs)
# исправление после проверки 1
# def risk_and_gross_profit_for_region(prediction_valid_geo):
def risk_and_gross_profit_for_region(prediction_valid_geo, target_geo):
# создаем список куда будем записывать значения валовой прибыли по 1000 рассмотренных случаев
gross_profit_geo = []
# счетчик выборок с отрицательной валовой прибылью
risk_count=0
# Запускаем 1000 рассмотрений
for i in range(ITERATION_NUMBER):
# из-за разных версий библиотек приводим к нужном типу предсказания
# prediction_valid_geo = pd.Series(prediction_valid_geo) # после проверки 1 эта операция сделана перед проверкой средних
# выбираем 500 случайных скважин
subsample_research_well = prediction_valid_geo.sample(n=RESEARCH_WELL, replace=True, random_state=state)
# из них берем 200 лучших
best_wells = subsample_research_well.sort_values(ascending=False).head(DEVELOPMENT_WELL)
# считаем валовую прибыль для 200 скважин и записываем это наш список
# g_p = gross_profit(best_wells) # исправление после проверки 1, для расчета прибыли по таргету, а не предсказанию
g_p = gross_profit(target_geo[best_wells.index])
gross_profit_geo.append(g_p)
if g_p < 0: risk_count += 1 # если валовая прибыль отрицательная, то увеличиваем счетчик на 1
# из-за разных версий библиотек приводим к нужном типу
gross_profit_geo = pd.Series(gross_profit_geo)
# считаем среднюю и значения для 95% доверительного интервала
mean_gross_profit_geo = gross_profit_geo.mean()
lower = gross_profit_geo.quantile(0.025)
upper = gross_profit_geo.quantile(0.975)
# выводим на экран
display('Среднее значение валовой прибыли ={:.0f}'.format(mean_gross_profit_geo))
display('95% доверительный интревал =({:.0f}, {:.0f})'.format(lower, upper))
display('Длина доверительного интервала {:.0f}'.format(upper - lower))
display('Риск убытка = {:.1f}%'.format(risk_count / ITERATION_NUMBER * 100))
gross_profit_geo.plot(kind='hist'
, title='Валовая прибыль'
# , xlabel='Валовая прибыль, сотни миллионов'
# , ylabel='Частота значения'
)
plt.xlabel('Валовая прибыль, сотни миллионов')
plt.ylabel('Частота значения, ед.')
plt.show()
Мы будем основываться на том, что валовая прибыль по конкретном региону будет определяться как средняя из 1000 значений.
А за риски будут отвечать 95% доверительный интервал: мы предпологаем что минимальная валовая прибыль будет равна или выше начала этого этого доверительного интервала. Если начало доверительного интервала положительное, то вероятность получить убыток равно или ниже 2.5%.
risk_and_gross_profit_for_region(prediction_valid_geo_0, target_valid_geo_0)
'Среднее значение валовой прибыли =445219809'
'95% доверительный интревал =(-28531509, 935589453)'
'Длина доверительного интервала 964120961'
'Риск убытка = 3.6%'
risk_and_gross_profit_for_region(prediction_valid_geo_1, target_valid_geo_1)
'Среднее значение валовой прибыли =438406602'
'95% доверительный интревал =(63853699, 818077991)'
'Длина доверительного интервала 754224292'
'Риск убытка = 1.1%'
risk_and_gross_profit_for_region(prediction_valid_geo_2, target_valid_geo_2)
'Среднее значение валовой прибыли =692599202'
'95% доверительный интревал =(189552769, 1166371541)'
'Длина доверительного интервала 976818771'
'Риск убытка = 0.9%'
risk_and_gross_profit_for_region(prediction_valid_geo_3, target_valid_geo_3)
'Среднее значение валовой прибыли =-3614995241'
'95% доверительный интревал =(-4149796248, -3063553912)'
'Длина доверительного интервала 1086242336'
'Риск убытка = 100.0%'
Мы достигли поставленных целей:
Это было круто.
Лучший регион в этом проекте geo_2.